package com.strato.mvc.api.v1_0_0.control.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.strato.logging.v1_0_0.api.Log;
import com.strato.logging.v1_0_0.api.LogFactory;
import com.strato.mvc.api.v1_0_0.control.HttpInvoker;
import com.strato.mvc.api.v1_0_0.control.PathVariable;
import com.strato.mvc.api.v1_0_0.control.PathVariableCollector;
import com.strato.mvc.api.v1_0_0.control.RequestMappingRegistry;
import com.strato.mvc.api.v1_0_0.control.impl.matcher.ConstantMatcher;
import com.strato.mvc.api.v1_0_0.control.impl.matcher.Matcher;
import com.strato.mvc.api.v1_0_0.control.impl.matcher.PathVariableMatcher;

public class DefaultRequestMappingRegistry implements RequestMappingRegistry {

	private Log log=LogFactory.getLog(getClass());
	
	private Node rootNode=new Node();
	
	@Override
	public void addRequestMapping(String urlpattern, List<PathVariable> pathVariables,HttpInvoker invoker) {
		urlpattern=standardizePattern(urlpattern);
		List<PathVariable> pvs=new ArrayList<PathVariable>(pathVariables);
		String[] parts=urlpattern.split("/");
		Node parentNode=rootNode;
		for(String part:parts){
			Matcher matcher=null;
			if(isPathVariablePart(part)){
				String varName=extractPathVariableName(part);
				PathVariable pv=pick(pvs,varName);
				if(pv==null){
					throw new IllegalArgumentException("Unknow path variable '"+part+"' for url pattern '"+urlpattern+"'");
				}
				matcher=new PathVariableMatcher(pv);
			}else{
				matcher=new ConstantMatcher(part);
			}
			Node subNode=addNode(parentNode.children,matcher,urlpattern);
			parentNode.children.add(subNode);
			subNode.matcher=matcher;
			parentNode=subNode;
		}
		if(!pvs.isEmpty()){
			throw new IllegalArgumentException("Pattern "+urlpattern+" does not match given path variables");
		}
		parentNode.invoker=invoker;
		log.info("Mapped /"+urlpattern+" to "+invoker);
	}

	private Node addNode(List<Node> nodes, Matcher matcher, String urlpattern) {
		Node addedNode=null;
		for(Node node:nodes){
			Matcher nmatcher=node.matcher;
			if(nmatcher.mequals(matcher)){//use exists
				addedNode=node;
				break;
			}else if(nmatcher.conflicts(matcher)){//conflict
				throw new IllegalArgumentException("There is a confiliction with "+urlpattern+" in "+matcher.toString());
			}
		}
		if(addedNode==null){
			addedNode=new Node();
			addedNode.matcher=matcher;
			nodes.add(addedNode);
		}
		return addedNode;
	}

	private String standardizePattern(String urlpattern) {
		if(!"".equals(urlpattern)&&!urlpattern.startsWith("/")){
			throw new IllegalArgumentException(urlpattern+" -> A not-empty url pattern must start with '/'");
		}
		if("".equals(urlpattern)){
			return urlpattern;
		}else{
			return urlpattern.substring(1);
		}
	}

	private PathVariable pick(List<PathVariable> pathVariables, String varName) {
		if(pathVariables==null){
			return null;
		}
		for(Iterator<PathVariable> iter=pathVariables.iterator();iter.hasNext();){
			PathVariable pv=iter.next();
			if(varName.equals(pv.getName())){
				iter.remove();
				return pv;
			}
		}
		return null;
	}

	private boolean isPathVariablePart(String part) {
		return part.startsWith("{")&&part.endsWith("}");
	}
	
	private String extractPathVariableName(String part){
		return part.substring(1,part.length()-1);
	}

	@Override
	public HttpInvoker getMappedInvoker(String url,PathVariableCollector pvc) {
		url=standardizePattern(url);
		String[] parts=url.split("/");
		Node lastNode=rootNode;
		for(String part:parts){
			boolean found=false;
			for(Node node:lastNode.children){
				int match=node.matcher.match(part, pvc);
				switch(match){
				case Matcher.MATCH:
					found=true;
					lastNode=node;
					break;
				case Matcher.NOT_MATCH:
					break;
				case Matcher.MATCH_SUCCEDENT:
					return node.invoker;
				}
			}
			if(!found){
				return null;
			}
		}
		return lastNode.invoker;
	}

	protected class Node{
		public HttpInvoker invoker;
		public List<Node> children=new ArrayList<Node>();
		public Matcher matcher;
	}
	
	private static DefaultRequestMappingRegistry instance=new DefaultRequestMappingRegistry();
	
	public static DefaultRequestMappingRegistry getInstance() {
		return instance;
	}

	@Override
	public void removeInvokersByBundleName(String bundleName) {
		Node node=rootNode;
		removeInvokersByBundleName(node,bundleName);
	}

	private void removeInvokersByBundleName(Node node, String bundleName) {
		HttpInvoker invoker = node.invoker;
		if(invoker!=null){
			if(bundleName.equals(invoker.getBundleName())){
				node.invoker=null;
			}
		}
		if(node.children!=null){
			for(Node child:node.children){
				removeInvokersByBundleName(child,bundleName);
			}
		}
	}
}
